Passing Data Between Screens in Flutter
Passing data between screens is a common requirement in Flutter applications. When a user selects an item, opens a profile, edits a product, selects a setting, or completes a form, one screen often needs to send information to another screen or receive a result back.
Flutter screens are widgets, and data can be passed to another screen through constructor parameters, route arguments, named-route arguments, or other application-state patterns. Flutter's official cookbook demonstrates passing objects directly through a screen constructor and also passing objects through RouteSettings or named-route arguments. :contentReference[oaicite:0]{index=0}
1. Why Pass Data Between Screens?
Applications frequently need to transfer information during navigation.
- Send a product from a product list to a product details screen.
- Send a user object to a profile screen.
- Send an article to an article details screen.
- Send an ID to fetch details from an API.
- Send form information to a confirmation screen.
- Send selected settings to another screen.
- Return a selected value from one screen to another.
- Return the result of a form or selection screen.
2. Basic Concept
Suppose we have two screens:
Screen A
|
| Send Data
↓
Screen B
For example:
Product List
|
| Product object
↓
Product Details
The Product List screen sends the selected product to the Product Details screen.
3. Main Ways to Pass Data
| Method | Use Case |
| Constructor parameters | Simple and type-safe direct navigation |
RouteSettings | Passing an object through a route |
| Named route arguments | Passing data with Navigator.pushNamed() |
onGenerateRoute | Centralized route and argument handling |
Navigator.pop() result | Returning data to the previous screen |
| Shared application state | Data needed by multiple unrelated screens |
| Routing packages | Complex navigation and deep-linking scenarios |
4. Passing Data Using Constructor Parameters
The simplest approach is to pass data directly through the destination widget's constructor.
Example
class ProfileScreen extends StatelessWidget {
final String name;
const ProfileScreen({
super.key,
required this.name,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Profile'),
),
body: Center(
child: Text(
'Welcome, $name',
style: const TextStyle(fontSize: 24),
),
),
);
}
}
Navigate to the screen:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProfileScreen(
name: 'Rahul',
),
),
);
Here, the value Rahul is passed from the first screen to ProfileScreen.
5. Passing Multiple Values
You can pass multiple values through constructor parameters.
class ProfileScreen extends StatelessWidget {
final String name;
final int age;
final String email;
const ProfileScreen({
super.key,
required this.name,
required this.age,
required this.email,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(name),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Name: $name'),
Text('Age: $age'),
Text('Email: $email'),
],
),
),
);
}
}
Navigate:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProfileScreen(
name: 'Rahul',
age: 25,
email: '[email protected]',
),
),
);
6. Passing a Custom Dart Object
When multiple values belong to the same entity, creating a model class is usually cleaner than passing many individual parameters.
class User {
final int id;
final String name;
final String email;
const User({
required this.id,
required this.name,
required this.email,
});
}
Create a user:
const user = User(
id: 101,
name: 'Rahul',
email: '[email protected]',
);
Pass it to another screen:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProfileScreen(
user: user,
),
),
);
Destination screen:
class ProfileScreen extends StatelessWidget {
final User user;
const ProfileScreen({
super.key,
required this.user,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(user.name),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('ID: ${user.id}'),
Text('Name: ${user.name}'),
Text('Email: ${user.email}'),
],
),
),
);
}
}
This pattern is particularly useful when the destination screen needs a complete object.
7. Complete Product Example
Flutter's official navigation cookbook demonstrates a similar pattern using a Todo model: the selected object is passed to a details screen through its constructor. :contentReference[oaicite:1]{index=1}
import 'package:flutter/material.dart';
class Product {
final int id;
final String name;
final double price;
const Product({
required this.id,
required this.name,
required this.price,
});
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
final products = [
const Product(
id: 1,
name: 'Laptop',
price: 65000,
),
const Product(
id: 2,
name: 'Smartphone',
price: 35000,
),
const Product(
id: 3,
name: 'Headphones',
price: 5000,
),
];
return MaterialApp(
debugShowCheckedModeBanner: false,
home: ProductListScreen(products: products),
);
}
}
class ProductListScreen extends StatelessWidget {
final List products;
const ProductListScreen({
super.key,
required this.products,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Products'),
),
body: ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return ListTile(
title: Text(product.name),
subtitle: Text('₹${product.price}'),
trailing: const Icon(Icons.arrow_forward),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailsScreen(
product: product,
),
),
);
},
);
},
),
);
}
}
class ProductDetailsScreen extends StatelessWidget {
final Product product;
const ProductDetailsScreen({
super.key,
required this.product,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(product.name),
),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.name,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
Text(
'Product ID: ${product.id}',
),
const SizedBox(height: 8),
Text(
'Price: ₹${product.price}',
style: const TextStyle(fontSize: 20),
),
],
),
),
);
}
}
8. Passing Data Using RouteSettings
Another approach is to attach data to a route using RouteSettings(arguments: ...).
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProductDetailsScreen(),
settings: RouteSettings(
arguments: product,
),
),
);
The destination screen can retrieve the object:
final product =
ModalRoute.of(context)!.settings.arguments as Product;
Flutter's official documentation demonstrates this approach for passing an object to a new screen. :contentReference[oaicite:2]{index=2}
9. Complete RouteSettings Example
class ProductDetailsScreen extends StatelessWidget {
const ProductDetailsScreen({super.key});
@override
Widget build(BuildContext context) {
final product =
ModalRoute.of(context)!.settings.arguments as Product;
return Scaffold(
appBar: AppBar(
title: Text(product.name),
),
body: Center(
child: Text(
'Price: ₹${product.price}',
style: const TextStyle(fontSize: 24),
),
),
);
}
}
Navigation:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProductDetailsScreen(),
settings: RouteSettings(
arguments: product,
),
),
);
10. Passing Data Using Named Routes
When using named routes, data can be passed through the arguments parameter of Navigator.pushNamed().
Navigator.pushNamed(
context,
'/profile',
arguments: user,
);
The destination can retrieve the argument:
final user =
ModalRoute.of(context)!.settings.arguments as User;
Flutter's official named-route recipe documents this approach. It also notes that named routes are no longer recommended for most new applications, so this technique is particularly useful when working with existing named-route applications or learning the API. :contentReference[oaicite:3]{index=3}
11. Complete Named Route Data Example
import 'package:flutter/material.dart';
class User {
final String name;
final String email;
const User({
required this.name,
required this.email,
});
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
routes: {
'/': (context) => const HomeScreen(),
'/profile': (context) => const ProfileScreen(),
},
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
const user = User(
name: 'Rahul',
email: '[email protected]',
);
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pushNamed(
context,
'/profile',
arguments: user,
);
},
child: const Text('Open Profile'),
),
),
);
}
}
class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});
@override
Widget build(BuildContext context) {
final user =
ModalRoute.of(context)!.settings.arguments as User;
return Scaffold(
appBar: AppBar(
title: const Text('Profile'),
),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Name: ${user.name}'),
Text('Email: ${user.email}'),
],
),
),
);
}
}
12. Passing Multiple Arguments
You can pass multiple values using a custom class.
class ScreenArguments {
final String title;
final String message;
const ScreenArguments({
required this.title,
required this.message,
});
}
Pass the arguments:
Navigator.pushNamed(
context,
'/details',
arguments: const ScreenArguments(
title: 'Flutter',
message: 'Welcome to Flutter development!',
),
);
Read them:
final args =
ModalRoute.of(context)!.settings.arguments as ScreenArguments;
Text(args.title);
Text(args.message);
13. Using onGenerateRoute for Data Passing
onGenerateRoute allows route arguments to be processed centrally.
MaterialApp(
onGenerateRoute: (settings) {
if (settings.name == '/profile') {
final user = settings.arguments as User;
return MaterialPageRoute(
builder: (context) {
return ProfileScreen(user: user);
},
);
}
return MaterialPageRoute(
builder: (context) => const HomeScreen(),
);
},
);
Navigate:
Navigator.pushNamed(
context,
'/profile',
arguments: user,
);
This approach keeps route-specific argument handling in one central location.
14. Returning Data from a Screen
Data does not always move only from the first screen to the second screen. Sometimes the second screen needs to send a result back to the first screen.
Screen A
|
| Open Screen B
↓
Screen B
|
| Return Result
↓
Screen A
Flutter's official cookbook uses Navigator.pop(context, result) to return a result from a screen. The original screen can await the Future returned by Navigator.push(). :contentReference[oaicite:4]{index=4}
15. Basic Return Data Example
Open the second screen:
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SelectionScreen(),
),
);
Return data from the second screen:
Navigator.pop(
context,
'Selected Item',
);
The returned value is received by the first screen through the result variable.
16. Complete Return Data Example
import 'package:flutter/material.dart';
void main() {
runApp(
const MaterialApp(
debugShowCheckedModeBanner: false,
home: HomeScreen(),
),
);
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State createState() => _HomeScreenState();
}
class _HomeScreenState extends State {
String selectedValue = 'Nothing selected';
Future openSelectionScreen() async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SelectionScreen(),
),
);
if (!mounted) return;
if (result != null) {
setState(() {
selectedValue = result;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Return Data Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
selectedValue,
style: const TextStyle(fontSize: 20),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: openSelectionScreen,
child: const Text('Choose Option'),
),
],
),
),
);
}
}
class SelectionScreen extends StatelessWidget {
const SelectionScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Select Option'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
Navigator.pop(
context,
'Option A',
);
},
child: const Text('Option A'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(
context,
'Option B',
);
},
child: const Text('Option B'),
),
],
),
);
}
}
17. Understanding Future in Navigation
When Navigator.push() opens another screen, it returns a Future. That Future completes when the pushed route is popped.
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SelectionScreen(),
),
);
Here:
String represents the type of result expected.
await waits for the destination screen to close.
- The returned value is stored in
result.
18. Returning an Integer
The returned value does not have to be a String.
final selectedId = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProductSelectionScreen(),
),
);
Return an integer:
Navigator.pop(
context,
101,
);
19. Returning a Boolean
Boolean results are useful for confirmation screens.
final confirmed = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ConfirmationScreen(),
),
);
Return the result:
Navigator.pop(context, true);
Then:
if (confirmed == true) {
print('User confirmed');
}
20. Returning a Custom Object
You can also return a complete object.
class Address {
final String city;
final String pincode;
const Address({
required this.city,
required this.pincode,
});
}
Receive the object:
final address = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const AddressScreen(),
),
);
Return it:
Navigator.pop(
context,
const Address(
city: 'Mumbai',
pincode: '400001',
),
);
21. Passing Data Through a Form
A common practical example is sending form data to a confirmation screen.
class RegistrationData {
final String name;
final String email;
const RegistrationData({
required this.name,
required this.email,
});
}
Navigate to confirmation:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ConfirmationScreen(
data: RegistrationData(
name: nameController.text,
email: emailController.text,
),
),
),
);
The confirmation screen receives the object through its constructor.
22. Practical E-Commerce Example
Suppose a product list contains:
Product(
id: 101,
name: 'Laptop',
price: 65000,
)
When the user taps the product:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailsScreen(
product: product,
),
),
);
The details screen displays:
Product Details
----------------------
Name: Laptop
ID: 101
Price: ₹65000
----------------------
[ Add to Cart ]
When the user adds the item to the cart, the details screen can return a result:
Navigator.pop(
context,
product,
);
The previous screen can receive the selected product:
final addedProduct = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailsScreen(
product: product,
),
),
);
23. Passing Data to an Edit Screen
Another common use case is editing an existing object.
class Profile {
final String name;
final String email;
const Profile({
required this.name,
required this.email,
});
}
Open the edit screen:
final updatedProfile = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EditProfileScreen(
profile: profile,
),
),
);
After editing:
Navigator.pop(
context,
updatedProfile,
);
The original screen can then update its UI with the returned profile.
24. Passing Data Through a ListView
Passing the selected item from a list to a detail screen is one of the most common Flutter navigation patterns.
ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return ListTile(
title: Text(product.name),
subtitle: Text('₹${product.price}'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailsScreen(
product: product,
),
),
);
},
);
},
)
The official Flutter cookbook uses the same general pattern with a list of objects and a details screen. :contentReference[oaicite:5]{index=5}
25. Passing Only an ID
You do not always need to send the entire object. Sometimes sending an ID is more appropriate.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailsScreen(
productId: 101,
),
),
);
The destination can then use the ID to obtain the latest product information from a repository or API.
class ProductDetailsScreen extends StatelessWidget {
final int productId;
const ProductDetailsScreen({
super.key,
required this.productId,
});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(
'Product ID: $productId',
),
),
);
}
}
26. Passing Data with go_router
For applications using go_router, data can be represented through path parameters, query parameters, or extra data depending on the routing requirement.
Path Parameter Example
GoRoute(
path: '/product/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return ProductDetailsScreen(
productId: id,
);
},
)
Navigate to:
context.go('/product/101');
The destination receives 101 as the product ID.
27. Passing Extra Data with go_router
For richer in-memory objects, go_router also supports the extra field.
context.push(
'/product-details',
extra: product,
);
Read the object:
GoRoute(
path: '/product-details',
builder: (context, state) {
final product = state.extra as Product;
return ProductDetailsScreen(
product: product,
);
},
)
When choosing between a URL parameter and an object passed in memory, consider whether the destination should be directly addressable through a URL. URL-based identifiers are particularly useful for deep links.
28. Data Passing and Deep Linking
There is an important difference between passing an object directly and passing information through a URL.
| Method | Example | Suitable For |
| Object through constructor | Product(product: product) | Direct in-app navigation |
| Route argument | arguments: product | Named-route applications |
| Path parameter | /product/101 | Deep links and URLs |
| Query parameter | ?category=laptop | Filter/search state |
| Shared state | Provider/other state solution | Data needed across multiple screens |
29. Handling Null Results
A user might close a screen without selecting or returning anything. Therefore, returned data should often be treated as nullable.
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SelectionScreen(),
),
);
if (result != null) {
print('Selected: $result');
} else {
print('No selection made');
}
30. Checking mounted After await
When a State object awaits navigation and then uses its BuildContext or updates state, it is important to account for the possibility that the widget was removed while waiting.
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SelectionScreen(),
),
);
if (!mounted) return;
setState(() {
selectedValue = result;
});
Flutter's official returning-data recipe explicitly checks context.mounted after the asynchronous navigation operation before using the context. :contentReference[oaicite:6]{index=6}
31. Data Flow Example
Product List
|
| Product object
↓
Product Details
|
| true / updated product
↓
Product List
This represents two-way navigation data flow:
- The first screen sends data to the second screen.
- The second screen optionally returns a result.
- The first screen receives and processes that result.
32. Passing Data Between Three Screens
Suppose we have:
Home
↓
Product Details
↓
Checkout
Home can pass the product:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailsScreen(
product: product,
),
),
);
Product Details can pass the same product to Checkout:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CheckoutScreen(
product: product,
),
),
);
33. Passing a List of Objects
Sometimes the destination needs multiple objects.
class CartScreen extends StatelessWidget {
final List products;
const CartScreen({
super.key,
required this.products,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Cart'),
),
body: ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(products[index].name),
subtitle: Text(
'₹${products[index].price}',
),
);
},
),
);
}
}
Navigate:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CartScreen(
products: cartProducts,
),
),
);
34. Passing Data from a Form
class UserFormData {
final String name;
final String email;
final String phone;
const UserFormData({
required this.name,
required this.email,
required this.phone,
});
}
Collect the data:
final data = UserFormData(
name: nameController.text,
email: emailController.text,
phone: phoneController.text,
);
Send it to the next screen:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ConfirmationScreen(
data: data,
),
),
);
35. Returning Form Data
An edit screen can return updated information:
Navigator.pop(
context,
updatedUser,
);
The previous screen receives it:
final updatedUser = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EditUserScreen(
user: user,
),
),
);
if (!mounted) return;
if (updatedUser != null) {
setState(() {
user = updatedUser;
});
}
36. Constructor vs Route Arguments
| Aspect | Constructor | Route Arguments |
| Type safety | Strong and explicit | Requires casting when using dynamic arguments |
| Readability | Very clear | Less direct |
| Direct navigation | Excellent | Useful but more indirect |
| Named routes | Not required | Commonly used |
| Large objects | Easy to model | Possible with arguments |
| Maintenance | Generally straightforward | Requires consistent argument handling |
37. Best Practices for Passing Data
- Use constructor parameters for straightforward direct navigation.
- Create model classes for related data instead of passing many unrelated parameters.
- Use typed objects wherever practical.
- Validate route arguments before casting them.
- Use IDs when the destination should load fresh data from a repository or API.
- Use URL/path parameters when information needs to be represented in a deep link.
- Use
Navigator.pop(context, result) when a screen needs to return a result.
- Handle nullable results when the user can leave without making a selection.
- Check
mounted after asynchronous navigation before calling setState() or using a context-dependent operation.
- Avoid passing very large or unnecessary objects between many screens.
- For application-wide shared data, consider an appropriate state-management architecture rather than repeatedly passing the same object through many screens.
38. Common Mistakes
Mistake 1: Forgetting Required Data
class ProfileScreen extends StatelessWidget {
final User user;
const ProfileScreen({
super.key,
required this.user,
});
}
Make sure the user object is provided when creating the screen.
Mistake 2: Incorrect Type Casting
final user =
ModalRoute.of(context)!.settings.arguments as User;
If another type is passed as an argument, the cast can fail. Ensure that the sender and receiver agree on the expected type.
Mistake 3: Forgetting to Return Data
If the previous screen awaits a result:
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SelectionScreen(),
),
);
The destination should return the expected value when appropriate:
Navigator.pop(context, 'Selected');
Mistake 4: Ignoring Null Results
A screen may be closed without returning a result, so handle null when appropriate.
Mistake 5: Updating a Removed Widget
After an asynchronous navigation operation, check mounted before updating a State object.
39. Practical Student Management Example
Consider a student list application.
class Student {
final int id;
final String name;
final String course;
const Student({
required this.id,
required this.name,
required this.course,
});
}
Send the selected student:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StudentDetailsScreen(
student: student,
),
),
);
Display the data:
class StudentDetailsScreen extends StatelessWidget {
final Student student;
const StudentDetailsScreen({
super.key,
required this.student,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(student.name),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('ID: ${student.id}'),
Text('Name: ${student.name}'),
Text('Course: ${student.course}'),
],
),
),
);
}
}
40. Practical Settings Example
A settings screen can return a selected theme mode.
Open Settings:
final selectedTheme = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SettingsScreen(),
),
);
Return the selection:
Navigator.pop(
context,
'dark',
);
Handle it:
if (!mounted) return;
if (selectedTheme == 'dark') {
print('Dark theme selected');
}
41. Practical Selection Example
A country-selection screen can return a selected country.
final country = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const CountryScreen(),
),
);
Country screen:
ListTile(
title: const Text('India'),
onTap: () {
Navigator.pop(context, 'India');
},
)
42. Data Passing Flow
┌──────────────────────┐
│ Screen A │
│ Product List │
└──────────┬───────────┘
│
│ Product object
↓
┌──────────────────────┐
│ Screen B │
│ Product Details │
└──────────┬───────────┘
│
│ Selected result
↓
┌──────────────────────┐
│ Screen A │
│ Updated UI │
└──────────────────────┘
43. Choosing the Right Approach
| Requirement | Approach |
| Simple direct screen navigation | Constructor parameters |
| Product list to product details | Pass model object through constructor |
| Existing named-route application | Named route arguments |
| Central route processing | onGenerateRoute |
| Return selected value | Navigator.pop(context, result) |
| Complex shared application state | State-management architecture |
| Deep-linkable product page | URL/path parameter |
| Complex routing/deep linking | Router-based solution such as go_router |
44. Interview Questions
Q1. How can you pass data between Flutter screens?
Data can be passed through constructor parameters, route arguments, RouteSettings, named-route arguments, or a suitable application-state architecture.
Q2. What is the simplest way to pass data to another screen?
For direct navigation, passing data through the destination widget's constructor is a straightforward approach.
Q3. How do you pass data using named routes?
Use the arguments parameter of Navigator.pushNamed().
Q4. How do you retrieve named-route arguments?
Use ModalRoute.of(context)!.settings.arguments or process the arguments in onGenerateRoute().
Q5. How do you return data to the previous screen?
Use Navigator.pop(context, result).
Q6. What does Navigator.push return?
It returns a Future that completes when the pushed route is popped, optionally containing the returned result.
Q7. Why should you check mounted after await?
The widget may have been removed while waiting for the navigation Future. Checking mounted prevents updating or using a context from a widget that is no longer active.
Q8. Should you always pass the entire object to another screen?
No. Depending on the application, passing an ID and loading the latest data can be more appropriate, especially when the destination needs fresh data or the route needs to be deep-linkable.
Q9. Can a custom object be passed between screens?
Yes. Dart objects can be passed through constructors and can also be passed through route arguments.
45. Practice Exercise
Create a Flutter application with the following screens:
- Home Screen
- Product List Screen
- Product Details Screen
- Cart Screen
- Edit Product Screen
- Confirmation Screen
Implement the following:
- Create a
Product model.
- Display multiple products using
ListView.builder.
- Pass the selected product to the Product Details screen.
- Display the product's name, ID, price, and description.
- Pass the product to the Edit Product screen.
- Return the updated product using
Navigator.pop().
- Display the updated product on the previous screen.
- Pass a list of products to the Cart screen.
- Return a Boolean confirmation result from the Confirmation screen.
- Handle
null results and check mounted after asynchronous navigation.
46. Quick Revision
| Concept | Code | Purpose |
| Constructor data | Screen(data: value) | Directly pass data to a widget |
| Push screen | Navigator.push() | Open another screen |
| Route arguments | RouteSettings(arguments: value) | Attach data to a route |
| Named route arguments | arguments: value | Pass data with pushNamed() |
| Read arguments | ModalRoute.of(context) | Retrieve route data |
| Dynamic arguments | onGenerateRoute | Process arguments centrally |
| Return result | Navigator.pop(context, result) | Send data back |
| Receive result | await Navigator.push() | Wait for returned data |
| Check widget state | mounted | Ensure State is still active |
| URL parameter | /product/:id | Pass data through a route path |
47. Key Takeaways
- Passing data between screens is a fundamental Flutter navigation concept.
- Constructor parameters provide a clear way to pass data during direct navigation.
- Custom model classes are useful for passing structured data.
RouteSettings can attach arguments to a route.
- Named routes can receive data through the
arguments parameter.
ModalRoute.of(context) can retrieve route arguments.
onGenerateRoute can centralize route and argument handling.
Navigator.pop(context, result) can return data to the previous screen.
Navigator.push() returns a Future that can be awaited for a result.
- Always consider nullable results when the user can leave a screen without selecting anything.
- Check
mounted after asynchronous navigation before updating widget state.
- For deep-linkable routes, URL/path parameters can be more appropriate than passing only an in-memory object.
- For complex navigation requirements, a modern Router-based solution such as
go_router can be considered.
48. Official Flutter Resources
49. Learn Flutter with JustAcademy
To learn Flutter development, Dart programming, widgets, navigation, state management, APIs, Firebase integration, UI development, and practical Flutter projects, explore these resources:
JustAcademy Flutter Training Course
Register for Flutter Course Demo